/*
 * Copyright (c) 2009. Silenus Consultoria, S.L.
 */
package com.tonbeller.jpivot.mondrian;

import com.tonbeller.tbutils.res.Resources;

import com.tonbeller.wcf.table.AbstractTableModel;
import com.tonbeller.wcf.table.DefaultTableRow;
import com.tonbeller.wcf.table.TableRow;

import mondrian.olap.MemoryLimitExceededException;

import mondrian.rolap.RolapConnectionProperties;

import mondrian.util.MemoryMonitor;
import mondrian.util.MemoryMonitorFactory;

import org.apache.log4j.Logger;

import java.sql.*;

import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.sql.DataSource;


/**
 * A wcf table model for drill through data, requires an sql query and connection information to be set.
 */
public class MondrianDrillThroughTableModel extends AbstractTableModel {
	private static Logger logger = Logger.getLogger(MondrianDrillThroughTableModel.class);
	private static Context jndiContext;
	private String title = "Drill Through Table";
	private String caption = "";
	private String sql = "";
	private String jdbcUser;
	private String jdbcUrl;
	private String jdbcPassword;
	private String jdbcDriver;
	private String dataSourceName;
	private DataSource dataSource;
	private boolean ready = false;
	private TableRow[] rows = new TableRow[0];
	private String[] columnTitles = new String[0];
	private Resources resources;
	private Resources drillThroughResources;

/**
   * Constructor.
   */
	public MondrianDrillThroughTableModel() {
	}

	/**
	 * Retrieves the row count.
	 *
	 * @return the row count.
	 */
	public int getRowCount() {
		if(!ready) {
			executeQuery();
		}

		return rows.length;
	}

	/**
	 * Retrieves a row.
	 *
	 * @param rowIndex the index.
	 *
	 * @return the row.
	 */
	public TableRow getRow(int rowIndex) {
		if(!ready) {
			executeQuery();
		}

		return rows[rowIndex];
	}

	/**
	 * Retrieves the title.
	 *
	 * @return the title.
	 */
	public String getTitle() {
		return title;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getSql() {
		return sql;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param sql
	 */
	public void setSql(String sql) {
		this.sql = sql;
		this.ready = false;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param title
	 */
	public void setTitle(String title) {
		this.title = title;
	}

	/**
	 * wcf table component calls this method from it's constructor to get the number of columns
	 *
	 * @return the column count.
	 */
	public int getColumnCount() {
		if(!ready) {
			executeQuery();
		}

		return columnTitles.length;
	}

	/**
	 * Retireves a column title.
	 *
	 * @param columnIndex the column index.
	 *
	 * @return the column title.
	 */
	public String getColumnTitle(int columnIndex) {
		if(!ready) {
			executeQuery();
		}

		return columnTitles[columnIndex];
	}

	/**
	 * execute sql query
	 *
	 * @throws MemoryLimitExceededException
	 */
	private void executeQuery() {
		Connection con = null;

		class Listener implements MemoryMonitor.Listener {
			String oomMsg;

			Listener() {
			}

			public void memoryUsageNotification(long used, long max) {
				StringBuffer buf = new StringBuffer(200);

				buf.append("OutOfMemory used=");
				buf.append(used);
				buf.append(", max=");
				buf.append(max);

				if(dataSourceName != null) {
					buf.append(" for data source: ");
					buf.append(dataSourceName);
				} else if(jdbcUrl != null) {
					buf.append(" for jcbc URL: ");
					buf.append(jdbcUrl);
				}

				this.oomMsg = buf.toString();
			}

			void check() throws MemoryLimitExceededException {
				if(oomMsg != null) {
					throw new MemoryLimitExceededException(oomMsg);
				}
			}
		}

		Listener listener = new Listener();
		MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor();

		try {
			mm.addListener(listener);

			con = getConnection();

			Statement s = con.createStatement();
			ResultSet rs = s.executeQuery(sql);
			ResultSetMetaData md = rs.getMetaData();
			int numCols = md.getColumnCount();

			columnTitles = new String[numCols];

			// check for OutOfMemory
			listener.check();

			String newCol;
			boolean isKey;
			String temp;

			// set column headings
			for(int i = 0; i < numCols; i++) {
				//    columns are 1 based
				columnTitles[i] = md.getColumnName(i + 1).trim();

				// Added column mapping
				if(drillThroughResources != null) {
					isKey = columnTitles[i].indexOf("(Key)") > -1;

					if(isKey) {
						temp = columnTitles[i].replace("(Key)", "").trim();
						newCol = drillThroughResources.getOptionalString(temp, temp);
						newCol += (" (" + drillThroughResources.getOptionalString("Key", "Key") + ")");
					} else {
						newCol = drillThroughResources.getOptionalString(columnTitles[i], columnTitles[i]);
					}

					if(newCol != null) {
						columnTitles[i] = newCol;
					}
				}
			}

			title = resources.getString("DrillThrough.title") + " " + columnTitles[columnTitles.length - 1];

			// loop through rows
			List tempRows = new ArrayList();

			while(rs.next()) {
				Object[] row = new Object[numCols];

				// loop on columns, 1 based
				for(int i = 0; i < numCols; i++) {
					row[i] = rs.getObject(i + 1);
				}

				tempRows.add(new DefaultTableRow(row));

				// check for OutOfMemory
				listener.check();
			}

			rs.close();
			rows = (TableRow[])tempRows.toArray(new TableRow[0]);
		} catch(Exception e) {
			e.printStackTrace();
			logger.error("?", e);
			// problem occured, set table model to zero size
			rows = new TableRow[1];
			columnTitles = new String[1];
			columnTitles[0] = resources.getString("DrillThrough.error");

			Object[] row = new Object[1];

			row[0] = e.toString();
			rows[0] = new DefaultTableRow(row);
			ready = false;

			return;
		} finally {
			try {
				con.close();
			} catch(Exception e1) {
				// ignore
			}

			mm.removeListener(listener);
		}

		ready = true;
	}

	/**
	 * get sql connection
	 *
	 * @return the connection.
	 *
	 * @throws SQLException if something goes wrong.
	 * @throws RuntimeException if something goes wrong.
	 */
	private Connection getConnection() throws SQLException {
		if((dataSource == null) && (dataSourceName == null)) {
			if(jdbcUrl == null) {
				throw new RuntimeException(
					"Mondrian Connect string '" + "' must contain either '" + RolapConnectionProperties.Jdbc + "' or '"
					+ RolapConnectionProperties.DataSource + "'");
			}

			return DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword);
		} else {
			return getDataSource().getConnection();
		}
	}

	private DataSource getDataSource() {
		if(dataSource == null) {
			// Get connection from datasource.
			try {
				dataSource = (DataSource)getJndiContext().lookup(dataSourceName);
			} catch(NamingException e) {
				throw new RuntimeException("Error while looking up data source (" + dataSourceName + ")", e);
			}
		}

		return dataSource;
	}

	private Context getJndiContext() throws NamingException {
		if(jndiContext == null) {
			jndiContext = new InitialContext();
		}

		return jndiContext;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getJdbcDriver() {
		return jdbcDriver;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param jdbcDriver
	 */
	public void setJdbcDriver(String jdbcDriver) {
		this.jdbcDriver = jdbcDriver;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getJdbcPassword() {
		return jdbcPassword;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param jdbcPassword
	 */
	public void setJdbcPassword(String jdbcPassword) {
		this.jdbcPassword = jdbcPassword;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getJdbcUrl() {
		return jdbcUrl;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param jdbcUrl
	 */
	public void setJdbcUrl(String jdbcUrl) {
		this.jdbcUrl = jdbcUrl;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getJdbcUser() {
		return jdbcUser;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param jdbcUser
	 */
	public void setJdbcUser(String jdbcUser) {
		this.jdbcUser = jdbcUser;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getCaption() {
		return caption;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param caption
	 */
	public void setCaption(String caption) {
		this.caption = caption;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @return
	 */
	public String getDataSourceName() {
		return dataSourceName;
	}

	/**
	 * DOCUMENT ME!
	 *
	 * @param string
	 */
	public void setDataSourceName(String string) {
		dataSourceName = string;
	}

	/**
	 * Allow support for external data sources
	 *
	 * @param externalDataSource the external datasource to use
	 */
	public void setExternalDataSource(DataSource externalDataSource) {
		this.dataSource = externalDataSource;
	}

	/**
	 * Retrieves the resources.
	 *
	 * @return the resources.
	 */
	public Resources getResources() {
		return resources;
	}

	/**
	 * Sets the resources.
	 *
	 * @param resources the resources.
	 */
	public void setResources(final Resources resources) {
		this.resources = resources;
	}

	/**
	 * Sets the drill through resources.
	 *
	 * @param drillThroughResources the drill through resources.
	 */
	public void setDrillThroughResources(final Resources drillThroughResources) {
		this.drillThroughResources = drillThroughResources;
	}
}
